/*	
*	File: icmp-header.h
*	Date: 2011/11/04
*	User: Ronmaru
*	Desctiption: icmp header
*/

#ifndef ICMP_HEADER_H
#define ICMP_HEADER_H

#include <istream>
#include <ostream>
#include <algorithm>

// ICMP header for both IPv4 and IPv6.
//
// The wire format of an ICMP header is:
// 
// 0               8               16                             31
// +---------------+---------------+------------------------------+      ---
// |               |               |                              |       ^
// |     type      |     code      |          checksum            |       |
// |               |               |                              |       |
// +---------------+---------------+------------------------------+    8 bytes
// |                               |                              |       |
// |          identifier           |       sequence number        |       |
// |                               |                              |       v
// +-------------------------------+------------------------------+      ---

const int ICMP_MAX_HEADER_LENGTH = 8;

class ICMPheader
{
public:
  enum { 
    echo_reply = 0, 
    destination_unreachable = 3, 
    source_quench = 4,
    redirect = 5, 
    echo_request = 8, 
    time_exceeded = 11, 
    parameter_problem = 12,
    timestamp_request = 13, 
    timestamp_reply = 14, 
    info_request = 15,
    info_reply = 16, 
    address_request = 17, 
    address_reply = 18 
  };

  ICMPheader() { std::fill(rep_, rep_ + sizeof(rep_), 0); }

  void SetType(unsigned char n) { rep_[0] = n; }
  void SetCode(unsigned char n) { rep_[1] = n; }
  void SetChecksum(unsigned short n) { Encode(2, 3, n); }
  void SetIdentifier(unsigned short n) { Encode(4, 5, n); }
  void SetSequenceNumber(unsigned short n) { Encode(6, 7, n); }

  unsigned char GetType() const { return rep_[0]; }
  unsigned char GetCode() const { return rep_[1]; }
  unsigned short GetChecksum() const { return Decode(2, 3); }
  unsigned short GetIdentifier() const { return Decode(4, 5); }
  unsigned short GetSequenceNumber() const { return Decode(6, 7); }

  friend std::istream& operator>>(std::istream& is, ICMPheader& header)
  { return is.read(reinterpret_cast<char*>(header.rep_), ICMP_MAX_HEADER_LENGTH); }

  friend std::ostream& operator<<(std::ostream& os, const ICMPheader& header)
  { return os.write(reinterpret_cast<const char*>(header.rep_), ICMP_MAX_HEADER_LENGTH); }

private:
  unsigned short Decode(int a, int b) const
  { return (rep_[a] << 8) + rep_[b]; }

  void Encode(int a, int b, unsigned short n)
  {
    rep_[a] = static_cast<unsigned char>(n >> 8);
    rep_[b] = static_cast<unsigned char>(n & 0xFF);
  }

  unsigned char rep_[ICMP_MAX_HEADER_LENGTH];
};

template <typename Iterator>
void ComputeChecksum(ICMPheader& header,
  Iterator body_begin, Iterator body_end)
{
  unsigned int sum = (header.GetType() << 8) + header.GetCode()
    + header.GetIdentifier() + header.GetSequenceNumber();

  Iterator body_iter = body_begin;
  while (body_iter != body_end)
  {
    sum += (static_cast<unsigned char>(*body_iter++) << 8);
    if (body_iter != body_end)
      sum += static_cast<unsigned char>(*body_iter++);
  }

  sum = (sum >> 16) + (sum & 0xFFFF);
  sum += (sum >> 16);
  header.SetChecksum(static_cast<unsigned short>(~sum));
}

#endif // ICMP_HEADER_H